<?php

/**
 * @copyright Michiel Hakvoort 2010
 * @license http://www.opensource.org/licenses/bsd-license.php New BSD
 * @package mangrove
 * @subpackage grove
 * @filesource
 */

/*
 * Copyright (c) 2010 Michiel Hakvoort
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 */

namespace mg;

use Iterator, Countable, Closure, ArrayAccess, PDO, AppendIterator, IteratorAggregate, Traversable, IteratorIterator;

interface ResultSet extends IteratorAggregate, Countable {

    /**
     * Enter description here...
     *
     * @return \mg\Row
     */
    public function getRow();

    /**
     * Visit all rows in a ResultSet
     *
     * @param function($param1, $param2, $paramN)
     *
     * @param Closure $function
     */
    public function visit(Closure $function);
}

class RollupResultSet implements ResultSet {

    public function getIterator() {
        return null;
    }

    public function getRow() {
        return null;
    }

    public function count() {
        return null;
    }

    public function visit(Closure $function) {
        return null;
    }
}

class UnionResultSet implements ResultSet {

    /**
     * Enter description here...
     *
     * @var \AppendIterator
     */
    protected $iterator = null;

    public function __construct(ResultSet $first, ResultSet $second) {
        $this->iterator = new AppendIterator();
        $this->iterator->append(new IteratorIterator($first));
        $this->iterator->append(new IteratorIterator($second));
    }

    public function getIterator() {
        return $this->iterator;
    }

    public function count() {
        return null;
    }

    public function getRow() {
        return null;
    }

    public function visit(Closure $function) {
        return null;
    }
}

class PDOResultSet implements ResultSet {


    /**
     * Enter description here...
     *
     * @var \PDOStatement
     */
    private $statement = null;

    /**
     * @var \Iterator
     */
    private $iterator;

    public function __construct(\PDOStatement $statement) {
        $this->statement = $statement;
        $this->iterator = new PDOStatementIterator($statement);
    }

    public function getIterator() {
        return $this->iterator;
    }

    public function count() {
        return $this->statement->rowCount();
    }

    /**
     * Enter description here...
     *
     * @return \mg\Row
     */
    public function getRow() {
        if(!$this->iterator->valid()) {
            $this->iterator->rewind();
        }

        $result = $this->iterator->current();

        $this->iterator->next();

        return $result;
    }

    public function visit(Closure $function) {
        $functionAbstract = new \ReflectionFunction($function);
        $parameters = $functionAbstract->getParameters();

        $parametersArray = array();

        reset($parameters);

        while(($each = each($parameters)) !== false) {
            list($_, $parameter) = $each;
            $parametersArray[$parameter->getName()] = null;
        }


        while(($row = $this->getRow()) !== null) {
            foreach($parametersArray as $key => $_) {
                $parametersArray[$key] = $row->$key;
            }

            $functionAbstract->invokeArgs($parametersArray);
        }
    }
}

class PDOStatementIterator implements Iterator {

    private $currentRow = null;
    private $key		= 0;

    /**
     * Enter description here...
     *
     * @var \PDOStatement
     */
    private $statement = null;

    public function __construct(\PDOStatement $statement) {
        $this->statement = $statement;
        $this->currentRow = new Row();
        $this->populate();
    }

    private function populate() {
        $result = $this->statement->fetch(PDO::FETCH_ASSOC);

        if($result === false) {
            $this->currentRow = null;
            return;
        }

        foreach($result as $key=>$value) {
            $this->currentRow->$key = $value;
        }
    }

    public function current() {
        return $this->currentRow;
    }

    public function key() {
        return $this->key;
    }

    public function next() {
        $this->key++;
        $this->populate();
    }

    public function rewind() {
    }

    public function count() {
        return $this->statement->rowCount();
    }

    public function valid() {
        return $this->currentRow !== null;
    }
}

class Row implements ArrayAccess {

    public function __construct() {
    }

    public function offsetSet($offset, $value) {
        return $this->$offset;
    }

    public function offsetExists($offset) {
        return isset($this->$offset);
    }

    public function offsetGet($offset) {
        return $this->$offset;
    }

    public function offsetUnset($offset) {
    }
}
